/* *
 * --ライセンスについて--
 *
 * 「本ファイルの内容は Mozilla Public License Version 1.1 (「本ライセンス」)
 * の適用を受けます。
 * 本ライセンスに従わない限り本ファイルを使用することはできません。
 * 本ライセンスのコピーは http://www.mozilla.org/MPL/ から入手できます。
 *
 * 本ライセンスに基づき配布されるソフトウェアは、「現状のまま」で配布されるものであり、
 * 明示的か黙示的かを問わず、いかなる種類の保証も行われません。
 * 本ライセンス上の権利および制限を定める具体的な文言は、本ライセンスを参照してください。
 *
 * オリジナルコードおよび初期開発者は、N_H (h.10x64@gmail.com) です。
 *
 * N_H によって作成された部分の著作権表示は次のとおりです。
 *
 * Copyright (C)N_H 2012
 *
 * このファイルの内容は、上記に代えて、
 * GNU General License version2 以降 (以下 GPL とする)、
 * GNU Lesser General Public License Version 2.1 以降 (以下 LGPL とする)、
 * の条件に従って使用することも可能です。
 * この場合、このファイルの使用には上記の条項ではなく GPL または LGPL の条項が適用されます。
 * このファイルの他者による使用を GPL または LGPL の条件によってのみ許可し、
 * MPL による使用を許可したくない対象者は、上記の条項を削除することでその意思を示し、
 * 上記条項を GPL または LGPL で義務付けられている告知およびその他の条項に置き換えてください。
 * 対象者が上記の条項を削除しない場合、
 * 受領者は MPL または GPL または LGPL ライセンスのいずれによってもこのファイルを
 * 使用することができます。」
 *
 * -- License --
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License。You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND、either express or implied。See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Initial Developer of the Original Code is
 *   N_H (h.10x64@gmail.com).
 *
 * Portions created by the Initial Developer are Copyright (C)N_H 2012
 * the Initial Developer。All Rights Reserved.
 *
 * Alternatively、the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL")、or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above。If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL、and not to allow others to
 * use your version of this file under the terms of the MPL、indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL。If you do not delete
 * the provisions above、a recipient may use your version of this file under
 * the terms of any one of the MPL、the GPL or the LGPL.
 *
 * */
package com.magiciansforest.audio.soundrenderer.gui;

import com.magiciansforest.audio.soundrenderer.event.*;
import com.magiciansforest.audio.soundrenderer.logic.MMDCharacter;
import com.magiciansforest.audio.soundrenderer.logic.MMDSoundSource;
import com.magiciansforest.audio.soundrenderer.logic.Movie;
import de.lessvoid.nifty.controls.*;
import de.lessvoid.nifty.controls.button.ButtonControl;
import de.lessvoid.nifty.controls.dropdown.DropDownControl;
import de.lessvoid.nifty.controls.label.LabelControl;
import de.lessvoid.nifty.controls.listbox.ListBoxControl;
import de.lessvoid.nifty.controls.textfield.TextFieldControl;
import de.lessvoid.nifty.elements.Element;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author N_H
 */
public class NiftySoundPanel extends NiftyPanel implements SoundSourceChangeEventListener, CharacterChangeEventListener {

    private static final String LIST_REGEX = "\\[(\\d+)\\]\\s*(.*)";
    private static final String PARENT_MODEL_REGEX = "\\[(\\d+)\\]\\s*(.*)";
    private static final Pattern LIST_PATTERN = Pattern.compile(LIST_REGEX);
    private static final Pattern PARENT_MODEL_PATTERN = Pattern.compile(PARENT_MODEL_REGEX);
    private static final int GROUP_NO_ID = 1;
    private static final int GROPU_NO_NAME = 2;
    private DropDown<String> parentDropDown;
    private DropDown<String> boneDropDown;
    private Label idLabel;
    private TextField nameTextFieldControl;
    private TextField startFrameTextFieldControl;
    private TextField endFrameTextFieldControl;
    private TextField posXTextFieldControl;
    private TextField posYTextFieldControl;
    private TextField posZTextFieldControl;
    private TextField volumeTextFieldControl;
    private Button loadWavButton;
    private ListBox<String> soundSourceList;
    private String oldStartFrame;
    private String oldEndFrame;
    private String oldPosX, oldPosY, oldPosZ;
    private String oldVolume;
    private Label wavLabel;
    private long selectedSoundID = -1;
    private long selectedModelID = -1;
    private boolean doesUpdate = true;
    private HashMap<Long, String> idOldItemMap = new HashMap<Long, String>();
    private HashMap<Long, Integer> idIndexMap = new HashMap<Long, Integer>();

    public NiftySoundPanel() {
    }

    @Override
    public void bind(Movie mov, Element panel) {
        super.bind(mov, panel);

        //Parent DropDown
        this.parentDropDown = panel.findControl("parentDropDown", DropDownControl.class);
        this.boneDropDown = panel.findControl("boneDropDown", DropDownControl.class);
        //ID Label
        this.idLabel = panel.findControl("idLabel", LabelControl.class);
        //Name TextField
        this.nameTextFieldControl = panel.findControl("soundNameTextField", TextFieldControl.class);
        //StartFrame TextField
        this.startFrameTextFieldControl = panel.findControl("soundStartFrameTextField", TextFieldControl.class);
        startFrameTextFieldControl.setText("0");
        this.oldStartFrame = startFrameTextFieldControl.getText();
        //EndFrame TextField
        this.endFrameTextFieldControl = panel.findControl("soundEndFrameTextField", TextFieldControl.class);
        endFrameTextFieldControl.setText("0");
        this.oldEndFrame = endFrameTextFieldControl.getText();
        //X TextField
        this.posXTextFieldControl = panel.findControl("soundPosXTextField", TextFieldControl.class);
        posXTextFieldControl.setText("0");
        this.oldPosX = posXTextFieldControl.getText();
        //Y TextField
        this.posYTextFieldControl = panel.findControl("soundPosYTextField", TextFieldControl.class);
        posYTextFieldControl.setText("0");
        this.oldPosY = posYTextFieldControl.getText();
        //Z TextField
        this.posZTextFieldControl = panel.findControl("soundPosZTextField", TextFieldControl.class);
        posZTextFieldControl.setText("0");
        this.oldPosZ = posZTextFieldControl.getText();
        //Volume TextField
        this.volumeTextFieldControl = panel.findControl("soundVolumeTextField", TextFieldControl.class);
        volumeTextFieldControl.setText("0");
        this.oldVolume = volumeTextFieldControl.getText();
        //Wav Label
        this.wavLabel = panel.findControl("wavLabel", LabelControl.class);
        //LoadWav Button
        this.loadWavButton = panel.findControl("loadWavFileButton", ButtonControl.class);
        loadWavButton.setEnabled(false);
        //SoundSource ListBox
        this.soundSourceList = panel.findControl("soundListBox", ListBoxControl.class);

        mov.addSoundSourceChangeEventListener(this);
        mov.addCharacterChangeEventListener(this);
    }

    @Override
    public void SoundSourceChange(SoundSourceChangeEvent evt) {
        if (doesUpdate) {
            setSoundSource(selectedSoundID);
        }
        if (evt.getStatus() == SoundSourceChangeEvent.CREATED || evt.getStatus() == SoundSourceChangeEvent.REMOVED) {
            soundSourceList.clear();
            idIndexMap.clear();
            idOldItemMap.clear();

            Iterator<MMDSoundSource> itr = mov.getSourceIterator();
            while (itr.hasNext()) {
                MMDSoundSource src = itr.next();
                String item = soundNameToListItem(src.getID(), src.getName());
                soundSourceList.addItem(item);
                idIndexMap.put(src.getID(), soundSourceList.itemCount() - 1);
                idOldItemMap.put(src.getID(), item);
            }
        } else if (evt.getStatus() == SoundSourceChangeEvent.CHANGED && selectedSoundID == evt.getID()) {
            MMDSoundSource src = mov.getSoundSource(selectedSoundID);
            if (src != null && !src.getName().equals(idOldItemMap.get(src.getID()))) {
                String item = soundNameToListItem(src.getID(), src.getName());
                soundSourceList.insertItem(item, idIndexMap.get(selectedSoundID));
                soundSourceList.removeItemByIndex(idIndexMap.get(selectedSoundID) + 1);
                soundSourceList.selectItem(item);
                idOldItemMap.put(selectedSoundID, item);
            }
        }
    }

    @Override
    public void characterChange(CharacterChangeEvent e) {
        if (e.getStatus() == CharacterChangeEvent.CREATED || e.getStatus() == CharacterChangeEvent.REMOVED) {
            parentDropDown.clear();

            Iterator<MMDCharacter> itr = mov.getCharacterIterator();
            while (itr.hasNext()) {
                MMDCharacter c = itr.next();
                String item = modelNameToListItem(c.getID(), c.getName());
                parentDropDown.addItem(item);
            }

            setSoundSource(selectedSoundID);
        }
    }

    public void onParentDropDownSelectionChange(DropDownSelectionChangedEvent<String> evt) {
        if (doesUpdate) {
            MMDSoundSource src = mov.getSoundSource(selectedSoundID);
            if (src != null) {
                MMDCharacter c = mov.getCharacter(parseModelIDFromListItem(evt.getSelection()));
                //Bind source to the selected model
                doesUpdate = false;
                mov.bind(src.getID(), c.getID(), null);
                resetBoneDropDown(c.getID());
                doesUpdate = true;
            }
        }
    }

    public void onBoneDropDownSelectionChange(DropDownSelectionChangedEvent<String> evt) {
        if (doesUpdate) {
            MMDSoundSource src = mov.getSoundSource(selectedSoundID);
            if (src != null) {
                mov.bind(selectedSoundID, src.getParentID(), evt.getSelection());
            }
        }
    }

    public void onTextFieldChage(TextFieldChangedEvent evt) {
        if (doesUpdate) {
            Element elem = evt.getTextFieldControl().getElement();
            if (elem.equals(nameTextFieldControl.getElement())) {
                onNameTextFieldChange(evt.getText());
            } else if (elem.equals(startFrameTextFieldControl.getElement())) {
                onStartFrameTextFieldChange(evt.getText());
            } else if (elem.equals(endFrameTextFieldControl.getElement())) {
                onEndFrameTextFieldChange(evt.getText());
            } else if (elem.equals(posXTextFieldControl.getElement())) {
                onPosXTextFieldChange(evt.getText());
            } else if (elem.equals(posYTextFieldControl.getElement())) {
                onPosYTextFieldChange(evt.getText());
            } else if (elem.equals(posZTextFieldControl.getElement())) {
                onPosZTextFieldChange(evt.getText());
            } else if (elem.equals(volumeTextFieldControl.getElement())) {
                onVolumeTextFieldChange(evt.getText());
            }
        }
    }

    public void onSoundListSelectionChange(ListBoxSelectionChangedEvent<String> evt) {
        if (evt.getSelection().size() > 0) {
            String selected = evt.getSelection().get(0);
            if (selected != null) {
                Matcher matcher = LIST_PATTERN.matcher(selected);
                if (matcher.matches()) {
                    selectedSoundID = Long.parseLong(matcher.group(GROUP_NO_ID));
                } else {
                    selectedSoundID = -1;
                }
            } else {
                selectedSoundID = -1;
            }
        } else {
            selectedSoundID = -1;
        }
        setSoundSource(selectedSoundID);
    }

    public long getSelectedID() {
        return selectedSoundID;
    }

    private void onNameTextFieldChange(String str) {
        if (doesUpdate) {
            doesUpdate = false;
            mov.setSoundSourceName(selectedSoundID, str);
            doesUpdate = true;
        }
    }

    private void onStartFrameTextFieldChange(String str) {
        if (doesUpdate) {
            try {
                doesUpdate = false;

                if (str.equals("")) {
                    startFrameTextFieldControl.setText("0");
                }
                int startFrame = Integer.decode(startFrameTextFieldControl.getText());

                mov.setSoundSourceStartTime(selectedSoundID, startFrame);
                oldStartFrame = startFrameTextFieldControl.getText();

                //Update endFrameTextField if end frame changed
                String endFrameStr = Integer.toString(mov.getSoundSource(selectedSoundID).getEndFrame());
                if (!endFrameTextFieldControl.getText().equals(endFrameStr)) {
                    endFrameTextFieldControl.setText(endFrameStr);
                    oldEndFrame = endFrameStr;
                }
            } catch (NumberFormatException nfe) {
                startFrameTextFieldControl.setText(oldStartFrame);
            } finally {
                doesUpdate = true;
            }
        }
    }

    private void onEndFrameTextFieldChange(String str) {
        if (doesUpdate) {
            try {
                doesUpdate = false;

                if (str.equals("")) {
                    endFrameTextFieldControl.setText("0");
                }

                int endFrame = Integer.decode(endFrameTextFieldControl.getText());

                //Update selected sound source end frame
                mov.setSoundSourceEndTime(selectedSoundID, endFrame);
                oldEndFrame = endFrameTextFieldControl.getText();

                //Update startFrameTextField if start frame changed.
                String startFrameStr = Integer.toString(mov.getSoundSource(selectedSoundID).getStartFrame());
                if (!startFrameTextFieldControl.getText().equals(startFrameStr)) {
                    startFrameTextFieldControl.setText(startFrameStr);
                    oldStartFrame = startFrameStr;
                }
            } catch (NumberFormatException nfe) {
                endFrameTextFieldControl.setText(oldEndFrame);
            } finally {
                doesUpdate = true;
            }
        }
    }
    
    public void onPosXTextFieldChange(String str) {
        if (doesUpdate) {
            try {
                doesUpdate = false;

                if (str.equals("")) {
                    posXTextFieldControl.setText("0");
                }
                
                float x = Float.parseFloat(posXTextFieldControl.getText());
                float y = Float.parseFloat(posYTextFieldControl.getText());
                float z = Float.parseFloat(posZTextFieldControl.getText());
                
                mov.setTranslation(selectedSoundID, x, y, z);
                
                oldPosX = posXTextFieldControl.getText();
            } catch (NumberFormatException nfe) {
                posXTextFieldControl.setText(oldPosX);
            } finally {
                doesUpdate = true;
            }
        }
    }
    
    public void onPosYTextFieldChange(String str) {
        if (doesUpdate) {
            try {
                doesUpdate = false;

                if (str.equals("")) {
                    posYTextFieldControl.setText("0");
                }
                
                float x = Float.parseFloat(posXTextFieldControl.getText());
                float y = Float.parseFloat(posYTextFieldControl.getText());
                float z = Float.parseFloat(posZTextFieldControl.getText());
                
                mov.setTranslation(selectedSoundID, x, y, z);
                
                oldPosY = posXTextFieldControl.getText();
            } catch (NumberFormatException nfe) {
                posYTextFieldControl.setText(oldPosY);
            } finally {
                doesUpdate = true;
            }
        }
    }
    
    public void onPosZTextFieldChange(String str) {
        if (doesUpdate) {
            try {
                doesUpdate = false;

                if (str.equals("")) {
                    posZTextFieldControl.setText("0");
                }
                
                float x = Float.parseFloat(posXTextFieldControl.getText());
                float y = Float.parseFloat(posYTextFieldControl.getText());
                float z = Float.parseFloat(posZTextFieldControl.getText());
                
                mov.setTranslation(selectedSoundID, x, y, z);
                
                oldPosZ = posZTextFieldControl.getText();
            } catch (NumberFormatException nfe) {
                posZTextFieldControl.setText(oldPosZ);
            } finally {
                doesUpdate = true;
            }
        }
    }
    
    public void onVolumeTextFieldChange(String str) {
        if (doesUpdate) {
            try {
                doesUpdate = false;

                if (str.equals("")) {
                   volumeTextFieldControl.setText("0");
                }
                double vol = Integer.decode(volumeTextFieldControl.getText());

                mov.setSoundSourceVolume(selectedSoundID, Math.pow(10, vol / 20));
                oldVolume = volumeTextFieldControl.getText();
            } catch (NumberFormatException nfe) {
                volumeTextFieldControl.setText(oldVolume);
            } finally {
                doesUpdate = true;
            }
        }
    }
    
    private void setSoundSource(long id) {
        if (doesUpdate) {
            doesUpdate = false;
            MMDSoundSource src = mov.getSoundSource(id);
            if (src != null) {
                idLabel.setText(Long.toString(src.getID()));
                nameTextFieldControl.setText(src.getName());
                nameTextFieldControl.setEnabled(true);
                startFrameTextFieldControl.setText(Integer.toString(src.getStartFrame()));
                startFrameTextFieldControl.setEnabled(true);
                endFrameTextFieldControl.setText(Integer.toString(src.getEndFrame()));
                endFrameTextFieldControl.setEnabled(true);
                posXTextFieldControl.setText(Float.toString(src.getSource().getLocalTranslation().x));
                posXTextFieldControl.setEnabled(true);
                posYTextFieldControl.setText(Float.toString(src.getSource().getLocalTranslation().y));
                posYTextFieldControl.setEnabled(true);
                posZTextFieldControl.setText(Float.toString(src.getSource().getLocalTranslation().z));
                posZTextFieldControl.setEnabled(true);
                volumeTextFieldControl.setText(Double.toString(20 * Math.log10(src.getVolume())));
                volumeTextFieldControl.setEnabled(true);
                loadWavButton.setEnabled(true);
                String wavPath = src.getWavFilePath();
                if (wavPath != null) {
                    wavLabel.setText(wavPath.substring(src.getWavFilePath().lastIndexOf(File.separatorChar) + 1));
                } else {
                    wavLabel.setText("### NULL ###");
                }
                MMDCharacter c = mov.getCharacter(src.getParentID());
                if (c != null) {
                    parentDropDown.selectItem(c.getName());
                    resetBoneDropDown(c.getID());
                }
                parentDropDown.setEnabled(true);
                boneDropDown.setEnabled(true);
                doesUpdate = true;
            } else {
                doesUpdate = false;
                idLabel.setText("-");
                nameTextFieldControl.setText("### NULL ###");
                nameTextFieldControl.setEnabled(false);
                startFrameTextFieldControl.setText("-");
                startFrameTextFieldControl.setEnabled(false);
                endFrameTextFieldControl.setText("-");
                endFrameTextFieldControl.setEnabled(false);
                posXTextFieldControl.setText("-");
                posXTextFieldControl.setEnabled(false);
                posYTextFieldControl.setText("-");
                posYTextFieldControl.setEnabled(false);
                posZTextFieldControl.setText("-");
                posZTextFieldControl.setEnabled(false);
                volumeTextFieldControl.setText("-");
                volumeTextFieldControl.setEnabled(false);
                loadWavButton.setEnabled(false);
                wavLabel.setText("### NULL ###");
                parentDropDown.setEnabled(false);
                boneDropDown.setEnabled(false);
                doesUpdate = true;
            }
        }
    }

    private void resetBoneDropDown(long modelID) {
        //Reset boneDropDown
        boneDropDown.clear();
        MMDSoundSource src = mov.getSoundSource(selectedSoundID);
        for (String boneName : mov.getCharacter(modelID).getBoneNames()) {
            boneDropDown.addItem(boneName);
        }
        if (src.getBoneName() != null) {
            boneDropDown.selectItem(src.getBoneName());
        }
    }

    private String modelNameToListItem(long modelID, String modelName) {
        return PARENT_MODEL_REGEX.replace("\\[(\\d+)\\]", "[" + modelID + "]").replace("\\s*", " ").replace("(.*)", modelName);
    }

    private String soundNameToListItem(long soundID, String soundName) {
        return LIST_REGEX.replace("\\[(\\d+)\\]", "[" + soundID + "]").replace("\\s*", " ").replace("(.*)", soundName);
    }

    private long parseSoundIDFromListItem(String item) {
        Matcher m = LIST_PATTERN.matcher(item);
        if (m.matches()) {
            return Long.parseLong(m.group(GROUP_NO_ID));
        } else {
            return -1;
        }
    }

    private long parseModelIDFromListItem(String item) {
        Matcher m = PARENT_MODEL_PATTERN.matcher(item);
        if (m.matches()) {
            return Long.parseLong(m.group(GROUP_NO_ID));
        } else {
            return -1;
        }
    }
}
